#ifndef HASHMAP_H_
#define HASHMAP_H_

#include "util/LinkedList.h"

namespace util {

/*
 * Clase para contener objetos de la forma clave/valor. Esta implementada con el
 * metodo foo.compare(bar) para comparar claves (mas que nada para poder usar
 * std::string como clave).
 */
template<class K, class V> class HashMap {
public:
	HashMap();

	virtual ~HashMap();
	/*
	 * Agrega un valor y lo asocia con una determinada key.
	 */
	virtual void put(K* key, V* value);

	/*
	 * Obtiene el valor correspondiente a la key pasada por parametro.
	 */
	virtual V* get(K* key) throw (ElementNotFoundException);

	/*
	 * Elimina un valor correspondiente a la key pasada por parametro.
	 */
	virtual V* remove(K* key) throw (ElementNotFoundException);

	/*
	 * Indica si el hashmap contiene la key pasada por parametro.
	 */
	virtual bool containsKey(K* key);

	/*
	 * Indica si el hashmap contiene el valor pasado por parametro.
	 */
	virtual bool containsValue(V* value);

	/*
	 * Devuelve la cantidad de items (key-values) que hay en el hash.
	 */
	virtual int size();

	/*
	 * Indica si el hash esta vacio.
	 */
	virtual bool isEmpty();

	/*
	 * Elimina todos los elementos del hashmap.
	 */
	virtual void clear();

	virtual util::LinkedList<K*>* keySet();

private:
	/*
	 * Clase privada para las entradas del mapa.
	 */
	template<class Q, class B> class MapEntry {
	public:
		/*
		 * Constructor.
		 */
		MapEntry();

		/*
		 * Constructor. Recibe la clave y el valor de la entrada al mapa.
		 */
		MapEntry(Q key, B value);

		/*
		 * Destructor.
		 */
		~MapEntry();

		/*
		 * Metodo para recuperar la clave del elemento del mapa.
		 */
		Q* getKey();

		/*
		 * Metodo para recuperar el valor del elemento del mapa.
		 */
		B* getValue();

		/*
		 * Metodo para setear el valor de la entrada al mapa.
		 */
		void setValue(B value);

	private:
		Q key;
		B value;
	};

	/*
	 * Clase privada que funciona de matcher para encontrar en la lista de
	 * elementos un elemento pedido.
	 */
	template<class I, class T> class MapEntryMatcher: public Matcher<T> {
	public:
		/*
		 * Constructor.
		 */
		MapEntryMatcher();

		/*
		 * Constructor, recibe la clave a buscar.
		 */
		MapEntryMatcher(I* key);

		/*
		 * Destructor.
		 */
		~MapEntryMatcher();

		/*
		 * Metodo para matchear con otro objeto entrada.
		 * NOTA: Implementada con el metodo compare para poder usarla con
		 * std::string.
		 */
		bool matches(T entryToCompare);

	private:
		I* currentKey;
	};

	LinkedList<MapEntry<K, V>*> dataList;
	LinkedList<K*> keys;
};

template<class K, class V> HashMap<K, V>::HashMap() {

}

template<class K, class V> HashMap<K, V>::~HashMap() {

}

template<class K, class V> void HashMap<K, V>::put(K* key, V* value) {
	MapEntryMatcher<K, MapEntry<K, V>*> matcher(key);
	MapEntry<K, V>* entry;
	try {
		entry = this->dataList.search(&matcher);
		entry->setValue(*value);
	} catch (ElementNotFoundException &e) {
		entry = new MapEntry<K, V> (*key, *value);
		this->dataList.append(entry);
		this->keys.append(key);
	}
}
template<class K, class V> V* HashMap<K, V>::get(K* key)
		throw (ElementNotFoundException) {
	MapEntryMatcher<K, MapEntry<K, V>*> matcher(key);
	try {
		return (this->dataList.search(&matcher))->getValue();
	} catch (ElementNotFoundException &e) {
		throw ElementNotFoundException("La clave no existe en el mapa.");
	}
}

template<class K, class V> V* HashMap<K, V>::remove(K* key)
		throw (ElementNotFoundException) {
	MapEntryMatcher<K, MapEntry<K, V>*> matcher(key);
	MapEntry<K, V>* ret;
	for (int i = 0; i < this->keys.count(); i++) {
		if (matcher.matches(this->dataList.get(i))) {
			ret = this->dataList.remove(i);
			break;
		}
	}
	return ret->getValue();
}

template<class K, class V> bool HashMap<K, V>::containsKey(K* key) {
	MapEntryMatcher<K, MapEntry<K, V>*> matcher(key);
	try {
		this->dataList.search(&matcher);
		return true;
	} catch (ElementNotFoundException &e) {
		return false;
	}
}

template<class K, class V> bool HashMap<K, V>::containsValue(V* value) {
	// TODO implement me...
	return false;
}

template<class K, class V> int HashMap<K, V>::size() {
	return this->dataList.count();
}

template<class K, class V> bool HashMap<K, V>::isEmpty() {
	return this->dataList.isEmpty();
}

template<class K, class V> void HashMap<K, V>::clear() {
	return this->dataList.clear();
}

template<class K, class V> util::LinkedList<K*>* HashMap<K, V>::keySet() {
	return &this->keys;
}

template<class K, class V> template<class Q, class B> HashMap<K, V>::MapEntry<
		Q, B>::MapEntry() {

}

template<class K, class V> template<class Q, class B> HashMap<K, V>::MapEntry<
		Q, B>::MapEntry(Q key, B value) {
	this->key = key;
	this->value = value;
}

template<class K, class V> template<class Q, class B> HashMap<K, V>::MapEntry<
		Q, B>::~MapEntry() {

}

template<class K, class V> template<class Q, class B> Q* HashMap<K, V>::MapEntry<
		Q, B>::getKey() {
	return &(this->key);
}

template<class K, class V> template<class Q, class B> B* HashMap<K, V>::MapEntry<
		Q, B>::getValue() {
	return &(this->value);
}

template<class K, class V> template<class Q, class B> void HashMap<K, V>::MapEntry<
		Q, B>::setValue(B value) {
	this->value = value;
}

template<class K, class V> template<class I, class T> HashMap<K, V>::MapEntryMatcher<
		I, T>::MapEntryMatcher() {

}

template<class K, class V> template<class I, class T> HashMap<K, V>::MapEntryMatcher<
		I, T>::MapEntryMatcher(I* currentKey) {
	this->currentKey = currentKey;
}

template<class K, class V> template<class I, class T> HashMap<K, V>::MapEntryMatcher<
		I, T>::~MapEntryMatcher() {

}

template<class K, class V> template<class I, class T> bool HashMap<K, V>::MapEntryMatcher<
		I, T>::matches(T entryToCompare) {
	return ((entryToCompare != NULL) && (entryToCompare->getKey()->compare(
			*(this->currentKey)) == 0));
}

}

#endif /* HASHMAP_H_ */
